/***************************************************************
 * Name:      HiddenFrame.cpp
 * Purpose:   Code for SketchUp Ruby Extension EventRelay
 * Author:    pecan Heber
 * Created:   2008/10/21
 * Copyright: pecan Heber
 * License:   GPL
 **************************************************************/
/*
	This file is part of Eventrelay, a Ruby extension for SketchUp
	Copyright (C) 2008,2009 Pecan Heber

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#define is_eq ==

#include "wx/menu.h"
#include "wx/timer.h"
#include <wx/dynarray.h>
#include <wx/datetime.h>
#include <wx/msw/private.h>
#include <wx/tokenzr.h>
#include <wx/msgdlg.h>
#include <wx/sizer.h>
#include <wx/stdpaths.h>
#include <wx/xrc/xmlres.h>

//#include "windows.h"
#include "HiddenFrame.h"
#include "RubyClass.h"
#include "MSWrunmacro.h"
#include "debugging.h"
#include "menuutils.h"
//#include "MSWrunmacro.h"

extern HWND g_hWndSketchUp;
namespace
{
    int myTimerID = wxNewId();
}
// ----------------------------------------------------------------------------
//  Event Tables
// ----------------------------------------------------------------------------
// HiddenFrame event table
BEGIN_EVENT_TABLE(HiddenFrame, wxFrame)
        EVT_CLOSE     (HiddenFrame::OnClose)
        EVT_KEY_DOWN    (HiddenFrame::OnKeyDown)
        EVT_KEY_UP      (HiddenFrame::OnKeyUp)
        //-EVT_RIGHT_DOWN  (HiddenFrame::RMouseDown)
        //-EVT_RIGHT_UP    (HiddenFrame::RMouseUp)
        //-EVT_LEFT_DOWN   (HiddenFrame::LMouseDown)
        //-EVT_LEFT_UP     (HiddenFrame::LMouseUp)
        //-EVT_MIDDLE_DOWN (HiddenFrame::MMouseDown)
        //-EVT_MIDDLE_UP   (HiddenFrame::MMouseUp)
        //-WM_COMMAND
        //-EVT_MENU_RANGE(0, 65536, HiddenFrame::OnMenuSelected)
        EVT_CONTEXT_MENU   (HiddenFrame::OnContextMenu)
        EVT_TIMER(myTimerID, HiddenFrame::OnTimer)
        EVT_IDLE( HiddenFrame::OnIdle)

END_EVENT_TABLE()

// KeyBinder event table
BEGIN_EVENT_TABLE(KeyBinderDialog, wxDialog)
	EVT_BUTTON(wxID_APPLY, KeyBinderDialog::OnApply)
END_EVENT_TABLE()

// ----------------------------------------------------------------------------
HiddenFrame::HiddenFrame(wxWindow* parent, wxWindowID id, const wxString& title)
// ----------------------------------------------------------------------------
    :wxFrame( parent, id, title, wxPoint(40,900))
{
    m_bProcessed = false;
    m_SketchUpHandEmulation = false;
    m_TimerInterval = 0;
    m_pMenuBar = 0;
    m_ExecuteFolder = wxEmptyString;
    m_UsrCmdFolder = wxEmptyString;
    m_UsrCmdFilePath = wxEmptyString;

    m_pTimer = new wxTimer(this,myTimerID);

    //Reminder if needed:
    //    Connect( wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED,
    //                        (wxObjectEventFunction) (wxEventFunction)
    //                        (wxCommandEventFunction) &HiddenFrame::OnClose );
}
// ----------------------------------------------------------------------------
HiddenFrame::~HiddenFrame()
// ----------------------------------------------------------------------------
{
    //dtor
    if (m_pTimer && m_pTimer->IsRunning() )
        m_pTimer->Stop();
}
// ----------------------------------------------------------------------------
void HiddenFrame::SetProcessed(wxEvent& event, bool trueFalse)
// ----------------------------------------------------------------------------
{
    if (false == trueFalse)
        event.Skip();
    //if ( false == m_bProcessed) this didn't work very well
    //    m_bProcessed = trueFalse;
    m_bProcessed = not event.GetSkipped();
}
// ----------------------------------------------------------------------------
void HiddenFrame::OnClose(wxCloseEvent& event)
// ----------------------------------------------------------------------------
{
    //asm("int3"); /*trap*/
}
// ----------------------------------------------------------------------------
void HiddenFrame::OnIdle(wxIdleEvent& event)
// ----------------------------------------------------------------------------
{
    //asm("int3"); /*trap*/
}
// ----------------------------------------------------------------------------
void HiddenFrame::OnTimer(wxTimerEvent& event)
// ----------------------------------------------------------------------------
{
    // TimerPop. Inform Ruby objects that have registered timer requests.

    m_TimeStop = wxDateTime::UNow();
    m_TimeDiff = m_TimeStop.Subtract(m_TimeStart);
    LONG timerInterval = m_TimeDiff.GetMilliseconds().ToLong();

    // Remove exhausted requests, and update others
    TimerQueUpdate( timerInterval );

    // Inform Ruby objects of any timer events
    for (int i = 0; i < (int)m_OnTimerPopObjects.GetCount(); ++i )
        GetRubyClassHandler()->Call_Ruby_OnERTimerEvent( m_OnTimerPopObjects.Item(i));
    m_OnTimerPopObjects.Empty();

    // Restart the timer if any work to do
    TimerStart();
}
// ----------------------------------------------------------------------------
void HiddenFrame::TimerQueAdd( ULONG objectIdIn, LONG millisecsIn)
// ----------------------------------------------------------------------------
{
    // Insert time into timer queue, stop timer, update time queue.
    // queue any resulting timer events, re-start timer.
    // Execute timer event queue.

    TimerStop();
    m_TimerQue[objectIdIn] =  millisecsIn;
    TimerStart();
}
// ----------------------------------------------------------------------------
bool HiddenFrame::TimerQueRemove( ULONG ObjectId)
// ----------------------------------------------------------------------------
{
    // Called by ERTimerStop() script method.
    // Remove time from timer queue, stop timer, update time queue.
    // queue any resulting timer events, re-instate timer.
    // Execute timer event queue.

    // Verify this object has an active timer request
    ObjectTimerHash::iterator it = m_TimerQue.find(ObjectId);
    if ( it == m_TimerQue.end())
        return false;

    TimerStop();

    int rc = m_TimerQue.erase( ObjectId );
    TimerQueUpdate( m_TimerInterval );
    // Restart the timer if any work to do
    TimerStart();

    return rc;
}
// ----------------------------------------------------------------------------
int HiddenFrame::TimerQueUpdate( long millInterval)
// ----------------------------------------------------------------------------
{
    if (0 == millInterval)
        return 0;
    ObjectTimerHash::iterator it;
    for( it = m_TimerQue.begin(); it != m_TimerQue.end(); ++it )
    {
        // long key = it->first, value = it->second; {objectVALUE, milliseconds}
        // do something useful with key and value
        VALUE RubyObject = it->first;
        LONG millisecs   = it->second;
        millisecs -= millInterval;
        if (millisecs <= 0)
        {
            m_OnTimerPopObjects.Add(RubyObject);
        }
        else
            it->second = millisecs;
    }

    // Remove popped items from the timer queue
    for ( int i=0; i < (int)m_OnTimerPopObjects.GetCount(); ++i )
        m_TimerQue.erase(m_OnTimerPopObjects.Item(i));

    return m_OnTimerPopObjects.GetCount();
}
// ----------------------------------------------------------------------------
void HiddenFrame::TimerStart()
// ----------------------------------------------------------------------------
{
    // Inform Ruby objects of any timer events
    for (int i = 0; i < (int)m_OnTimerPopObjects.GetCount(); ++i )
        GetRubyClassHandler()->Call_Ruby_OnERTimerEvent( m_OnTimerPopObjects.Item(i));
    m_OnTimerPopObjects.Empty();

    if ( m_TimerQue.size() == 0)
        return;

    // Start the timer with the smallest millisecond request
    long millisecs = 0;
    ObjectTimerHash::iterator it;
    for( it = m_TimerQue.begin(); it != m_TimerQue.end(); ++it )
    {
        // long key = it->first, value = it->second; {objectVALUE, milliseconds}
        // do something useful with key and value
        //VALUE RubyObject = it->first;
        LONG millisecsRequest   = it->second;
        if (not millisecs)
                millisecs = millisecsRequest;
        if (millisecsRequest < millisecs)
            millisecs = millisecsRequest;
    }
    if (millisecs)
    {
        m_pTimer->Start( millisecs, true );
        m_TimeStart = wxDateTime::UNow();
    }
}
// ----------------------------------------------------------------------------
void HiddenFrame::TimerStop()
// ----------------------------------------------------------------------------
{
    if (not m_pTimer->IsRunning() )
        return;

    m_pTimer->Stop();
    m_TimeStop = wxDateTime::UNow();
    m_TimeDiff = m_TimeStop - m_TimeStart;
    m_TimerInterval = m_TimeDiff.GetMilliseconds().ToLong();
    if (m_TimerInterval)
        TimerQueUpdate(m_TimerInterval);
}
// ----------------------------------------------------------------------------
void HiddenFrame::OnKeyDown(wxKeyEvent& event)
// ----------------------------------------------------------------------------
{
    // Key Down event

    #define MAPVK_VK_TO_VSC 0
    #define MAPVK_VSC_TO_VK 1
    #define MAPVK_VK_TO_CHAR 2
    #define MAPVK_VSC_TO_VK_EX 3

    bool rc = false;
    do
    {
        if (not GetRubyClassHandler() )
            break;
        // event.GetKeyCode() returns a WXK_KETCODE, not the VK_KEYCODE
        // Get the scanCode and translate to VK_KEYCODE
        // The docs say GetRawKeycode() returns the scanCode. That is incorrect.
        // It return the virtual key code.
        UINT key = event.GetRawKeyCode();
        UINT repeat = 1;
        UINT wxkbFlags = event.GetModifiers();
        // Translate wxWidgets flags to Ruby flags
        UINT flags = 0;
        flags |= (wxkbFlags & wxMOD_ALT)?ALT_MODIFIER_MASK:0 ;
        flags |= (wxkbFlags & wxMOD_CONTROL)?COPY_MODIFIER_MASK:0 ;
        flags |= (wxkbFlags & wxMOD_SHIFT)?CONSTRAIN_MODIFIER_MASK:0 ;
        #if defined(LOGGING)
        //UINT wxKey = event.GetKeyCode();
        //LOGIT( _T("OnKeyDown DLL VkCode[%d], ScanCode[%d], repeat[%d] flags[%d]"),
        //                    m_KeyVkCode, m_KeyScanCode, m_KeyRepeatCount, m_KeyFlags);
        //LOGIT( _T("OnKeyDown event Handler wxKey[%d], rawKey[%d], repeat[%d] flags[%d]"),
        //                    wxKey, key, repeat, flag);
        #endif
        rc = GetRubyClassHandler()->Call_Ruby_OnKeyDownMethod(
                            key, repeat, flags);
    }while(0);

    SetProcessed( event, rc);

}
// ----------------------------------------------------------------------------
void HiddenFrame::OnKeyUp(wxKeyEvent& event)
// ----------------------------------------------------------------------------
{
    // Key Up event
    #if defined(LOGGING)
    //LOGIT( _T("OnKeyUp event Handler"));
    #endif
    bool rc = false;
    do
    {
        if (not GetRubyClassHandler() )
            break;
        UINT key = event.GetRawKeyCode();
        UINT repeat = 1;
        UINT wxkbFlags = event.GetModifiers();
        // Translate wxWidgets flags to Ruby flags
        UINT flags = 0;
        flags |= (wxkbFlags & wxMOD_ALT)?ALT_MODIFIER_MASK:0 ;
        flags |= (wxkbFlags & wxMOD_CONTROL)?COPY_MODIFIER_MASK:0 ;
        flags |= (wxkbFlags & wxMOD_SHIFT)?CONSTRAIN_MODIFIER_MASK:0 ;

        rc = GetRubyClassHandler()->Call_Ruby_OnKeyUpMethod(
                            key, repeat, flags);
    }while(0);

    SetProcessed( event, rc);
}
// Keyboard flag documentation
//    The following key modifier constants are defined:
//
//
//        enum wxKeyModifier
//        {
//            wxMOD_NONE      = 0x0000,
//            wxMOD_ALT       = 0x0001,
//            wxMOD_CONTROL   = 0x0002,
//            wxMOD_ALTGR     = wxMOD_ALT | wxMOD_CONTROL,
//            wxMOD_SHIFT     = 0x0004,
//            wxMOD_META      = 0x0008,
//        #if defined(__WXMAC__) || defined(__WXCOCOA__)
//            wxMOD_CMD       = wxMOD_META,
//        #else
//            wxMOD_CMD       = wxMOD_CONTROL,
//        #endif
//            wxMOD_ALL       = 0xffff
//        };
//
//    Notice that wxMOD_CMD should be used instead of wxMOD_CONTROL in portable
//      code to account for the fact that although CONTROL modifier exists under
//      Mac OS, it is not used for the same purpose as under Windows or Unix there
//      while the special Mac-specific COMMAND modifier is used in exactly the
//      same way.

// ----------------------------------------------------------------------------
////void HiddenFrame::RMouseDown(wxMouseEvent& event)
////// ----------------------------------------------------------------------------
////{
////    #if defined(LOGGING)
////    //LOGIT( _T("HiddenFrame::RMouseDown event Handler"));
////    #endif
////    //asm("int3"); /*trap*/
////
////    bool rc = false;
////    UINT mouseEventType = GetRubyClassHandler()->m_typeRMouseDown;
////    rc = CommonMouseEvent( event, mouseEventType);
////
////    SetProcessed( event, rc);
////
////    //Doc: SketchUp/Ruby definitions
////    //Ruby Constant	Key
////    //CONSTRAIN_MODIFIER_KEY	Shift Key (Mac and PC)
////    //CONSTRAIN_MODIFIER_MASK	Shift Key (Mac and PC)
////    //COPY_MODIFIER_KEY	        Menu (Mac) / Ctrl (PC)
////    //COPY_MODIFIER_MASK	    Alt (Mac) / Ctrl (PC)
////    //ALT_MODIFIER_KEY	        Command (Mac) / Menu (PC)
////    //ALT_MODIFIER_MASK	        Command (Mac) / Alt (PC)
////
////    //    // Taken from SketchUp testing
////    //    enum {
////    //        CONSTRAIN_MODIFIER_KEY  =  16,  //0x10
////    //        CONSTRAIN_MODIFIER_MASK =  4,   //0x04 b 0000 0100
////    //        COPY_MODIFIER_KEY       =  17,  //0x11
////    //        COPY_MODIFIER_MASK      =  8,   //0x08 b 0000 1000
////    //        ALT_MODIFIER_KEY        =  18,  //0x12
////    //        ALT_MODIFIER_MASK       =  32,  //0x20 b 0020 0000
////    //    };
////    // ----------------------------------------------------------------
////    //     Original proof of concept to implement RMouseDown Sketchup HandDolly
////    // ----------------------------------------------------------------
////    //    #if defined(LOGGING)
////    //    LOGIT( _T("RMouseDown event Handler"));
////    //    #endif
////    //    // If mouse is moving, use RMouseButton as the Hand Tool
////    //    // Means user is not hovering for the context menu
////    //    wxPoint pos1 = wxGetMousePosition();
////    //    Sleep(200);
////    //    wxPoint pos2 = wxGetMousePosition();
////    //    if ( ((abs(pos1.x - pos2.x) <4)) && ((abs(pos1.y - pos2.y) <4)) )
////    //    {   // No mouse movement. User must be waiting for the context menus
////    //        SetProcessed(event,false);
////    //        return;
////    //    }
////    //    wxString macroKeys = _T("{LSHIFT DOWN}{MBUTTON DOWN}");
////    //    m_pMacroRunner->SendMacroString( macroKeys, this);
////    //    #if defined(LOGGING)
////    //    //LOGIT( _T("PostMessage rc[%d %ld]msg[%d]wParam[%d]lParam[%d %d]"), rc, error, message, wParam, HIWORD(lParam), LOWORD(lParam));
////    //    #endif
////    //    SetProcessed( event, true);
////    //    m_SketchUpHandEmulation = true;
////}
////// ----------------------------------------------------------------------------
////void HiddenFrame::RMouseUp(wxMouseEvent& event)
////// ----------------------------------------------------------------------------
////{
////    #if defined(LOGGING)
////    //LOGIT( _T("HiddenFrame::RMouseUp event Handler"));
////    #endif
////
////    bool rc = false;
////    UINT mouseEventType = GetRubyClassHandler()->m_typeRMouseUp;
////    rc = CommonMouseEvent( event, mouseEventType);
////
////    SetProcessed( event, rc);
////
////    // ----------------------------------------------------------------
////    // Original proof of concept to implement RMouseDown Sketchup HandDolly
////    // ----------------------------------------------------------------
////    //// Send SketchUp an UnShifted MiddleMouseUP command
////    //// to release the Hand Tool
////    //if ( not m_SketchUpHandEmulation )
////    //{   SetProcessed( event, false);
////    //    return;
////    //}
////    //// Our Sketchup Hand Tool Emulation is On. Turn it off
////    //// by posting an Unshifted MiddleMouseUp command.
////    //wxString macroKeys = _T("{MBUTTON UP}{LSHIFT UP}");
////    //m_pMacroRunner->SendMacroString( macroKeys, this);
////    //
////    //SetProcessed( event, true);
////    //m_SketchUpHandEmulation = false;
////}
////// ----------------------------------------------------------------------------
////void HiddenFrame::LMouseDown(wxMouseEvent& event)
////// ----------------------------------------------------------------------------
////{
////    // Left Mouse Down event
////
////    #if defined(LOGGING)
////    //LOGIT( _T("HiddenFrame::LMouseDown event Handler"));
////    #endif
////
////    bool rc = false;
////    UINT mouseEventType = GetRubyClassHandler()->m_typeLMouseDown;
////    rc = CommonMouseEvent( event, mouseEventType);
////
////    SetProcessed( event, rc);
////}
////// ----------------------------------------------------------------------------
////void HiddenFrame::LMouseUp(wxMouseEvent& event)
////// ----------------------------------------------------------------------------
////{
////    // Left Mouse Up event
////
////    #if defined(LOGGING)
////    //LOGIT( _T("HiddenFrame::LMouseUp event Handler"));
////    #endif
////
////    bool rc = false;
////    UINT mouseEventType = GetRubyClassHandler()->m_typeLMouseUp;
////    rc = CommonMouseEvent( event, mouseEventType);
////
////    SetProcessed( event, rc);
////}
////// ----------------------------------------------------------------------------
////void HiddenFrame::MMouseDown(wxMouseEvent& event)
////// ----------------------------------------------------------------------------
////{
////    // Middle Mouse Down event
////
////    #if defined(LOGGING)
////    //LOGIT( _T("HiddenFrame::MMouseDown event Handler"));
////    #endif
////
////    bool rc = false;
////    UINT mouseEventType = GetRubyClassHandler()->m_typeMMouseDown;
////    rc = CommonMouseEvent( event, mouseEventType);
////
////    SetProcessed( event, rc);
////}
////// ----------------------------------------------------------------------------
////void HiddenFrame::MMouseUp(wxMouseEvent& event)
////// ----------------------------------------------------------------------------
////{
////    // Middle Mouse Up event
////
////    #if defined(LOGGING)
////    //LOGIT( _T("HiddenFrame::MMouseUp event Handler"));
////    #endif
////
////    bool rc = false;
////    UINT mouseEventType = GetRubyClassHandler()->m_typeMMouseUp;
////    rc = CommonMouseEvent( event, mouseEventType);
////
////    SetProcessed( event, rc);
////}
////// ----------------------------------------------------------------------------
////bool HiddenFrame::CommonMouseEvent(wxMouseEvent& event, unsigned typeMouseEvent)
////// ----------------------------------------------------------------------------
////{
////    // Translate MS windows mouse flags to Ruby SketchUp flags
////
////    bool rc = false;
////    do
////    {
////        if (not GetRubyClassHandler() )
////            break;
////        UINT flags = 0;
////        flags |= (event.m_shiftDown ? CONSTRAIN_MODIFIER_MASK : 0);
////        flags |= (event.m_controlDown ? COPY_MODIFIER_MASK : 0);
////        flags |= (event.m_altDown ? ALT_MODIFIER_MASK : 0);
////        UINT x = event.GetX();
////        UINT y = event.GetY();
////        rc = GetRubyClassHandler()->Call_Ruby_OnMouseEventMethod( typeMouseEvent, flags, x, y);
////    }while(0);
////
////    return rc;
////}
////// ----------------------------------------------------------------------------
////void HiddenFrame::OnMenuSelected(wxCommandEvent& event)
////// ----------------------------------------------------------------------------
////{
////    // Menu selected event
////
////    bool rc = false;
////
////    do
////    {
////        if (not GetRubyClassHandler() )
////            break;
////        UINT menuId = event.GetId();
////        wxString label = GetMSWndMenuLabel(menuId);
////        #if defined(LOGGING)
////        LOGIT( _T("OnMenuSelected event Handler[%d]Label[%s]"), menuId, label.c_str());
////        #endif
////        rc = GetRubyClassHandler()->Call_Ruby_OnMenuSelected( menuId, label );
////    }while(0);
////
////    SetProcessed( event, rc);
////
////}
// ----------------------------------------------------------------------------
void HiddenFrame::OnContextMenu(wxContextMenuEvent& event)
// ----------------------------------------------------------------------------
{
    bool rc = false;
    //get the foreground window (dialog)
    HWND hTopWin = ::GetTopWindow(0);
    #if defined(LOGGING)
    LOGIT( _T("OnContextMenu main[%p] top[%p]"), g_hWndSketchUp, hTopWin);
    #endif

    // get class of top window
    TCHAR lpszClassName[80] = {0};
    TCHAR lpszTitle[80] = {0};
    int nLen = GetWindowTextLength(hTopWin);
    if (nLen)
        GetWindowText(hTopWin, lpszTitle, nLen);
    // A context menu is class "#32768"
    nLen = ::GetClassName(hTopWin, lpszClassName, 20);
    #if defined(LOGGING)
    int id = GetDlgCtrlID(hTopWin);
    LOGIT( _T("Found top window Title[%s] class[%s] id[%d]"), lpszTitle, lpszClassName, id);
    #endif

    //-wxString menuLabel = GetMSWndMenuLabel(hTopWin, 50019);
    SetProcessed( event, rc);;
}
// ----------------------------------------------------------------------------
unsigned HiddenFrame::GetMenuIdFromPath(const wxString menuPathIn)
// ----------------------------------------------------------------------------
{
    // Find id of menu item from path. eg, "Edit/Component/Save As"
    int menuId = 0;

    // Get the latest SketchUp menu structure
    RebuildHiddenMenuBar();

    // parse the menuPath into separate menus
    wxStringTokenizer tkz(menuPathIn, wxT("/"));
    wxString menuLabel = tkz.GetNextToken();
    int index = m_pMenuBar->FindMenu(menuLabel);
    if (wxNOT_FOUND == index) return 0;
    wxMenu* pMenu = m_pMenuBar->GetMenu(index);

    while ( tkz.HasMoreTokens() )
    {
        menuLabel = tkz.GetNextToken();
        //=menuId = pMenu->FindItem( menuLabel );
        menuId = FindMenuItem( pMenu, menuLabel );
        if (wxNOT_FOUND == menuId) break;
        //-wxMenuItem* pwxMenuItem = pMenu->FindItem( menuId, &pMenu);
        wxMenuItem* pwxMenuItem = FindMenuItem( pMenu, menuId, &pMenu);
        if (not pwxMenuItem) break;
        pMenu = pwxMenuItem->GetSubMenu();
        if (not pMenu) break;
    }

    if (menuId == wxNOT_FOUND)
        return 0;
    return menuId;
}
// ----------------------------------------------------------------------------
int HiddenFrame::RebuildHiddenMenuBar()
// ----------------------------------------------------------------------------
{

    SetMenuBar(0);
////    if ( m_pMenuBar )
////    {
////        //-m_pMenuBar->Destroy();
////        delete m_pMenuBar;
////        m_pMenuBar = 0;
////    }
    #if defined(LOGGING)
    LOGIT( _T("RebuildHiddenMenuBar[%s]"), _T("----------------------------"));
    #endif
    if (not m_pMenuBar)
        m_pMenuBar = new wxMenuBar();

    char topMenuLabel[256];
    wxZeroMemory(topMenuLabel);
    HMENU hMenu = ::GetMenu(g_hWndSketchUp);
    const int count = ::GetMenuItemCount(hMenu);
    wxMenu* pwxMenu = 0;

    for ( int i = 0; i < count; i++ )
    {
        HMENU hSubMenu;
        if ((hSubMenu = GetSubMenu(hMenu, i) ))
        {
            GetMenuString( hMenu, i, topMenuLabel, 255, MF_BYPOSITION );
            #if defined(LOGGING)
             //LOGIT( _T("MSW TopMenu[%s]"), topMenuLabel);
            #endif
            int prevIdx = m_pMenuBar->FindMenu(wxString(topMenuLabel));
            if (wxNOT_FOUND == prevIdx)
            {   // create new wxmenu item
                pwxMenu = new wxMenu();
                m_pMenuBar->Append( pwxMenu, wxString(topMenuLabel) );
            }
            else // wxmenu already exists
                pwxMenu = m_pMenuBar->GetMenu(prevIdx);

            MSWScanMenuBar( hSubMenu, pwxMenu, topMenuLabel );
        }

    }
    SetMenuBar(m_pMenuBar);
    #if defined(LOGGING)
      this->Show(true); //debgging
    #endif

    //Clean up else caller can't use the main window
    //win.Enable(true);
    //win.SetHWND(0);

    return count;

}//RebuildHiddenMenuBar
// ----------------------------------------------------------------------------
int HiddenFrame::MSWScanMenuBar(const HMENU hMenu, wxMenu* pwxMenu, const wxString menuPathIn)
// ----------------------------------------------------------------------------
{
    // Scan the MSW menus and create a like menu structure on our
    // hidden frame. It can then be used to verify requests and
    // act as a source for configuration routines.

    // -----------------------------------------------------------
    // This routine needs serious refactoring
    // -----------------------------------------------------------
    const HMENU hmenu = hMenu;
    MENUITEMINFO mii;
    wxZeroMemory(mii);
    mii.cbSize = sizeof(MENUITEMINFO);

    //mii.fMask = MIIM_TYPE | MIIM_DATA;
    mii.fMask = MIIM_STRING | MIIM_ID;
    #if defined(LOGGING)
    //LOGIT( _T("MSWScanMenuBar hMenu[%p] wxMenu[%p][%s]"), hMenu, pwxMenu, pwxMenu->GetTitle().c_str());
    #endif
    unsigned wwMenuPosn = 0;
    const int count = ::GetMenuItemCount(hmenu);
    for ( int i = 0; i < count; i++ )
    {
        // previous loop iteration could modify it, reset it back before
        // calling GetMenuItemInfo() to prevent it from overflowing dwTypeData
        mii.cch = 0;

        if ( ::GetMenuItemInfo(hmenu, i, TRUE, &mii) )
        {
            int MSWid = mii.wID;
            int textLen = mii.cch;
            if ( 0 == textLen ) continue; //label length is 0
            TCHAR szLabelText[textLen+1];
            mii.dwTypeData = szLabelText;
            mii.cch = textLen+1;
            if (not ::GetMenuItemInfo (hmenu, i, TRUE, &mii ))
                continue;
            MSWid = ::GetMenuItemID(hmenu, i);
            wxString MSWLabelText = wxString(szLabelText).Trim();

            // Ignore Recent files menu entries
            if ( (MSWLabelText[2]==_T(' ')) && MSWLabelText.Mid(1,1).IsNumber() )
                continue;
            // Clean up items beginning with counts, eg, "2 Groups", "3 Items"
            wxString tmpstr = MSWLabelText.BeforeFirst(_T(' '));
            if ( tmpstr.IsNumber() )
            {   //-continue;
                tmpstr = MSWLabelText.AfterFirst(_T(' '));
                tmpstr = tmpstr.BeforeFirst(_T(' '));
                MSWLabelText = tmpstr;
            }

            // Remove extraneous label info eg, "Components (1 in model)"
            if ( MSWLabelText.StartsWith(_T("Undo"))  )
                MSWLabelText = _T("Undo");
            if ( MSWLabelText.StartsWith(_T("Redo"))  )
                MSWLabelText = _T("Redo");
            if ( MSWLabelText.StartsWith(_T("Component ("))  )
                MSWLabelText = _T("Component");
            if ( MSWLabelText.StartsWith(_T("Group ("))  )
                MSWLabelText = _T("Group");
            // Replace incompatible sequences with blanks
            MSWLabelText.Replace(_T("/"), _T(" "), /*bool replaceAll =*/ true);
            MSWLabelText.Replace(_T("[Page Up]"), _T(" "), /*bool replaceAll =*/ true);
            MSWLabelText.Replace(_T("[Page Down]"), _T(" "), /*bool replaceAll =*/ true);
            MSWLabelText = MSWLabelText.Trim();

            wxMenu* pwxSubMenu = 0;
            wxMenuItem* pwxSubMenuItem = 0;
            wxString menuPath = menuPathIn + _T("/") + MSWLabelText;
            // make a hidden frame menu id
            int hfmID = MakeIdFromPath( menuPath );

            #if defined(LOGGING)
             //LOGIT(_T("MSWListMenu: posn[%d],Id[%d],text[%s]"),
             //        i, MSWid, MSWLabelText.c_str());
            #endif
            if (MSWid < 1) //null id or a submenu
            {
                if ( HMENU hSubMenu = ::GetSubMenu(hmenu, i) )
                {   // SubMenu item
                    int isDoneID = FindMenuItem( pwxMenu, MSWLabelText);
                    if ( wxNOT_FOUND == isDoneID )
                    {   // create new sub wxmenu item
                        pwxSubMenu = new wxMenu();
                        //-pwxSubMenuItem = new wxMenuItem(pwxMenu, MSWid, MSWLabelText, "", wxITEM_NORMAL, pwxSubMenu);
                        pwxSubMenuItem = new wxMenuItem(pwxMenu, hfmID, MSWLabelText, "", wxITEM_NORMAL, pwxSubMenu);
                        //pwxMenu->Append( pwxSubMenuItem );
                         if ( wwMenuPosn > pwxMenu->GetMenuItemCount() )
                                wwMenuPosn = pwxMenu->GetMenuItemCount();
                            pwxMenu->Insert( wwMenuPosn, pwxSubMenuItem );
                        #if defined(LOGGING)
                        //LOGIT( _T("SubMenu [%s] Inserted posn[%d]"), MSWLabelText.c_str(), wwMenuPosn);
                        #endif
                    }
                    else // wxmenu submenu already exists
                    {
                        pwxSubMenuItem = FindMenuItem( pwxMenu, hfmID, &pwxSubMenu);
                        pwxSubMenu = pwxSubMenuItem->GetSubMenu();
                        if (pwxSubMenu == 0)
                        {
                            #if defined(LOGGING)
                            LOGIT( _T("MSWScanMenuBar() err finding wxSubmenu[%s]"), MSWLabelText.c_str());
                            asm("int3"); /*trap*/
                            #endif
                        }
                    }
                    #if defined(LOGGING)
                      //LOGIT( _T("MSWScanMenuBar SubMenu hMenu[%p] wxMenu[%p]subMenu[%p][%s]"), hMenu, pwxMenu, pwxSubMenu ,pwxSubMenuItem->GetLabel().c_str());
                    #endif
                    MSWScanMenuBar( hSubMenu, pwxSubMenu, menuPathIn + _T("/") + MSWLabelText );
                    #if defined(LOGGING)
                      //LOGIT( _T("MSWScanMenuBar Pop hMenu[%p] wxMenu[%p][%s]"), hMenu, pwxMenu, pwxSubMenuItem->GetLabel().c_str());
                    #endif
                }//if
            }
            else
            {   // -------------------
                // Regular menu item
                // -------------------
                bool reset = false;
                wxMenu* pMSWIdMenu = 0;
                //-wxMenuItem* pMSWIdMenuItem = FindMenuItem( pwxMenu, MSWid, &pMSWIdMenu);
                wxMenuItem* pMSWIdMenuItem = FindMenuItem( pwxMenu, hfmID, &pMSWIdMenu);
                int isDoneID = FindMenuItem( pwxMenu, MSWLabelText);
                wxString isDoneLabel = wxEmptyString;
                wxMenu* pDoneIdMenu = 0;
                wxMenuItem* pDoneIdMenuItem = 0;
                if ( wxNOT_FOUND not_eq isDoneID)
                {
                    pDoneIdMenuItem = FindMenuItem( pwxMenu, isDoneID, &pDoneIdMenu);
                    if ( pDoneIdMenuItem )
                        isDoneLabel = pDoneIdMenuItem->GetItemLabel();
                    if ( pDoneIdMenu && (pDoneIdMenu not_eq pwxMenu) )
                    {// if duplicate found in submenu, treat as NOT FOUND
                        //asm("int3"); /*trap*/
                        isDoneID = wxNOT_FOUND;
                        pDoneIdMenu = 0;
                        pDoneIdMenuItem = 0;
                    }
                }
                if ( pMSWIdMenu && (pMSWIdMenu not_eq pwxMenu) )
                {   // if duplicate found in submenu, treat as NOT FOUND
                    //asm("int3"); /*trap*/
                    pMSWIdMenu = 0;
                    pMSWIdMenuItem = 0;
                }

                if ( not pMSWIdMenuItem )
                {   // -----------------------
                    // create new wxmenu item
                    // -----------------------
                    if (isDoneID not_eq wxNOT_FOUND)
                    {
                        //have old wxMenu item by this label
                        // refresh menu item that should have this MSW id
                        if (pDoneIdMenuItem) //-pMenu->Remove(isDoneID);
                        {
                            int oldId = pDoneIdMenuItem->GetId();
                            #if defined(LOGGING)
                            //LOGIT( _T("Refreshing ID [%s]fr[%d]to[%d]mswID[%d]"), pDoneIdMenuItem->GetItemLabel().c_str(),
                            //        oldId, hfmID, MSWid);
                            #endif

                            m_WxId2MSWIdMap.erase(oldId);
                            pDoneIdMenuItem->SetId(hfmID);
                            m_WxId2MSWIdMap[hfmID] = MSWid;
                            reset = true;
                        }
                    }
                    else //no previously created item by this id
                    {
                        pMSWIdMenuItem = new wxMenuItem(pwxMenu, hfmID, MSWLabelText, "", wxITEM_NORMAL, 0);
                        if ( wwMenuPosn > pwxMenu->GetMenuItemCount() )
                            wwMenuPosn = pwxMenu->GetMenuItemCount();
                        pwxMenu->Insert( wwMenuPosn, pMSWIdMenuItem );
                        m_WxId2MSWIdMap[hfmID] = MSWid;
                        #if defined(LOGGING)
                        //LOGIT( _T("Menu new [%s][%d] posn[%d]MSWid[%d]"), MSWLabelText.c_str(), hfmID, wwMenuPosn, MSWid);
                        #endif
                    }
                }
                else //have matching MSW menuItem
                {   // ----------------
                    //done; created previously
                    // ----------------
                    #if defined(LOGGING)
                    //LOGIT( _T("Menu old[%s]{%d} msw[%s]wx{%d}msw{%d}"), isDoneLabel.c_str(), isDoneID, MSWLabelText.c_str(), hfmID, MSWid);
                    #endif
                    // have msw id already
                    // check for changed id
                    if ( GetMappedMSWidFor(hfmID) not_eq  MSWid )
                    {
                        #if defined(LOGGING)
                        //LOGIT( _T("MSW ID changed[%s]old{%d}new{%d}"), isDoneLabel.c_str(), GetMappedMSWidFor(hfmID), MSWid);
                        #endif
                        m_WxId2MSWIdMap[hfmID] = MSWid;
                        reset = true;
                    }
                    // check for changed label
                    if (pMSWIdMenuItem->GetItemLabel() not_eq MSWLabelText)
                    {// changed label
                        #if defined(LOGGING)
                          //LOGIT( _T("Refreshing Label id[%d]fr[%s]to[%s]mswID[%d]"),
                          //              pMSWIdMenuItem->GetId(), pMSWIdMenuItem->GetItemLabel().c_str(),
                          //              MSWLabelText.c_str(), MSWid);
                        #endif
                        //Reset id's label to msw label
                        pMSWIdMenuItem->SetItemLabel(MSWLabelText);
                        // delete old idDoneID if not same as refreshed item
                        if ( pDoneIdMenuItem && (pDoneIdMenuItem not_eq pMSWIdMenuItem) )
                        {
                            #if defined(LOGGING)
                            //LOGIT( _T("Deleting[%d][%s]mswID[%d]"), pDoneIdMenuItem->GetId(), pDoneIdMenuItem->GetItemLabel().c_str(), MSWid);
                            #endif
                            //pMenuItem->SetItemLabel(_T("invalid"));
                            m_WxId2MSWIdMap.erase(pDoneIdMenuItem->GetId());
                            pDoneIdMenu->Delete(pDoneIdMenuItem->GetId());
                        }
                        reset = true;
                    }//if
                }//else have matching MSW id

                // Set status of item
                wxMenu* pMenu = 0;
                wxMenuItem* pMenuItem = FindMenuItem( pwxMenu, hfmID, &pMenu);
                UINT mswState = ::GetMenuState(hmenu, MSWid, MF_BYCOMMAND);
                if (pMenuItem)
                    pMenuItem->Enable( (mswState && MF_DISABLED)?false:true);
                #if defined(LOGGING)
                  if (reset) //verif
                  {
                        if (pMenuItem)
                        {
                          int mappedMSWid = GetMappedMSWidFor(hfmID);
                          //LOGIT( _T("Menu reset[%s][%d]pMenu[%p] mswID[%d]"),
                          //  pMenuItem->GetItemLabel().c_str(), pMenuItem->GetId(), pMenu, mappedMSWid);
                          if ( mappedMSWid ) {;}
                        }
                  }//if reset
                #endif
            }//else regular menu item
            wwMenuPosn += 1;
        }//if
        else // failed to get the menu text?
        {
            ;// it's not fatal, so don't show error, but still log it
            #if defined(LOGGING)
            LOGIT( _T("[%s]"), _T("MSWScanMenuBar failed"));
            #endif
        }
    }
    return count;
}
// ----------------------------------------------------------------------------
int HiddenFrame::MakeIdFromPath(const wxString menuPathIn)
// ----------------------------------------------------------------------------
{
    int id = 0;
    wxString menuPath = menuPathIn;
    menuPath.Replace(_T("&"),_T(""));
    menuPath = menuPath.BeforeFirst(_T('\t'));

    for (unsigned i = 0; i<menuPath.Length() ; ++i )
    {
        id += menuPath[i] * (i+1);
    }
    #if defined(LOGGING)
        // Verify that this algorithm is working.
        // No other MenuPath should have this id
        wxMenuItem* pMenuItem = m_pMenuBar->FindItem( id);
        if (pMenuItem)
        {
            wxString existingFullMenuPath = GetFullMenuPath( id );
            if ( menuPath not_eq existingFullMenuPath )
            {
                LOGIT( _T("MakeIdFromPath ssignment ERROR new[%s] matches old[%s][%d]"),
                    menuPath.c_str(),existingFullMenuPath.c_str(), id );
                asm("int3"); /*trap*/
            }
        }
    #endif
    return abs(id);
}
// ----------------------------------------------------------------------------
wxString HiddenFrame::GetFullMenuPath(int id)
// ----------------------------------------------------------------------------
{                                                           //(pecan 2008/12/14)

    // fetch the full menu path by way of a menu id
    // It will look like "File/Open/Recent Files"
    // or "" on failure

    wxString fullMenuPath = wxEmptyString;
    wxMenuBar* pbar = m_pMenuBar;

    wxMenu* pMenu = 0;

    // try to find the menu item
    wxMenuItem* pMenuItem = pbar->FindItem(id, &pMenu);
    if ( pMenuItem == NULL ) return fullMenuPath;

    // fetch wxMenuItem label
    fullMenuPath = pMenuItem->GetLabel();

    #if defined(LOGGING)
    //LOGIT( _T("fullMenuPath[%s]"), fullMenuPath.c_str() );
    #endif
    // get parent menu of the wxMenuItem
    wxMenu* pParentMenu = pMenu->GetParent();

    // prepend menu labels by iterating upwards through the menu structure
    while (pParentMenu)
    {    for (int i=0; i < (int)pParentMenu->GetMenuItemCount(); i++)
        {
            wxMenuItem* pitem = pParentMenu->GetMenuItems().Item(i)->GetData();
            if (pitem->GetSubMenu() && (pitem->GetSubMenu()== pMenu ))
            {
                fullMenuPath.Prepend( pitem->GetLabel() + wxT("/"));
                #if defined(LOGGING)
                //LOGIT( _T("ParentMenu[%s]"),pitem->GetLabel().c_str() );
                #endif
                break;
            }
        }
        pMenu = pParentMenu;
        pParentMenu = pParentMenu->GetParent();
    }//while

    // prepend last parent from menu bar
    for (int i=0; i<(int)pbar->GetMenuCount() ;++i )
    {
        wxMenu* pBarMenu = pbar->GetMenu(i);
        if ( pBarMenu == pMenu)
        {
            fullMenuPath.Prepend( pbar->GetLabelTop(i) + wxT("/"));
            #if defined(LOGGING)
            //LOGIT( _T("ParentMenu[%s]"),pbar->GetLabelTop(i).c_str() );
            #endif
        }
    }

    #if defined(LOGGING)
    //LOGIT( _T("fullPath[%s]"), fullMenuPath.c_str() );
    #endif

    return fullMenuPath;
}
// ----------------------------------------------------------------------------
int HiddenFrame::FindMenuItem(const wxMenu* pMenuIn, const wxString menuLabelIn)
// ----------------------------------------------------------------------------
{
    // Find menu item by label using this menu only. No submenu scanning
    int rc = wxNOT_FOUND;
    if ( not pMenuIn ) { return rc;}
    if ( menuLabelIn.IsEmpty() ) {return rc;}

    wxString menuLabel2Find = menuLabelIn;
    menuLabel2Find = menuLabel2Find.BeforeFirst(_T('\t'));
    menuLabel2Find.Replace(_T("&"),_T(""));

    //-wxMenuItemList menuItemList = pMenuIn->GetMenuItems();
    //-wxMenuItemList::iterator iter;

    for (unsigned i = 0; i<pMenuIn->GetMenuItemCount(); ++i)
    {
        wxMenuItem* pItem = pMenuIn->FindItemByPosition(i);
        if ( pItem->GetItemLabelText() == menuLabel2Find )
        {   rc = pItem->GetId();
            break;
        }
    }
    return rc;
}
// ----------------------------------------------------------------------------
wxMenuItem* HiddenFrame::FindMenuItem(const wxMenu* pMenuIn, const int menuIdIn, wxMenu** pMenuPtrIn)
// ----------------------------------------------------------------------------
{
    //Find menu item by id using this menu only, no submenu scanning

    wxMenuItem* pItemOut = 0;
    *pMenuPtrIn = 0;
    if ( not pMenuIn ) { return 0;}
    if ( menuIdIn < 1 ) {return 0;}
    //-wxMenuItemList menuItemList = pMenuIn->GetMenuItems();
    //-wxMenuItemList::iterator iter;
    for (unsigned i = 0; i<pMenuIn->GetMenuItemCount(); ++i)
    {
        wxMenuItem* pItem = pMenuIn->FindItemByPosition(i);
        if ( pItem->GetId() == menuIdIn )
        {   pItemOut = pItem;
            *pMenuPtrIn = pItem->GetMenu();
            break;
        }
    }
    return pItemOut;
}
// ----------------------------------------------------------------------------
int HiddenFrame::SendMenuActionAdd(const wxString menuPathIn)
// ----------------------------------------------------------------------------
{
    // Either invoke the menu path or queue it up for later execution.
    // If the top menu item needs to be updated first, send key macro
    // to invoke the menu, and queue the menuPath for later execution.

    m_SendMenuActionsAry.Add(menuPathIn);

    if ( menuPathIn.Contains(_T("Group")) || menuPathIn.Contains(_T("Component")) )
        m_pRubyClassHandler->m_pMacroRunner->SendMacroString( _T("!e{ALT}"), wxTheApp->GetTopWindow() );
    else
        SendMenuActionsNow();

    return m_SendMenuActionsAry.GetCount();
}
// ----------------------------------------------------------------------------
int HiddenFrame::SendMenuActionRemove(const wxString menuPathIn)
// ----------------------------------------------------------------------------
{
    // Remove menu path from commands to be executed
    m_SendMenuActionsAry.Remove(menuPathIn);
    return m_SendMenuActionsAry.GetCount();
}
// ----------------------------------------------------------------------------
int HiddenFrame::SendMenuActionsNow()
// ----------------------------------------------------------------------------
{
    // For each menu path in queue, find the menu id and issue a
    // MSW SendMessage(commandID) to invoke the menu item.
    // If the current menu structure does not match the menuPath from the
    // queue, issue a key macro to invoke the top level menu only.
    // This happens when the users Entity selection has re-structured the menu
    // system such that it nolonger contains the queued menu path.

    while( m_SendMenuActionsAry.GetCount() )
    {
        wxString menuPath = m_SendMenuActionsAry[0];
        m_SendMenuActionsAry.RemoveAt(0);

        // Rescan the MSW menu structure and validate our HiddenFrame menu
        int menuId = GetMenuIdFromPath(menuPath);

        wxMenu* pMenu = 0;
        wxMenuItem* pMnuItem = GetMenuBar()->FindItem(menuId, &pMenu);
        //wxString msg = wxString::Format(_T("Command \"%s\" not compatible with selection."), menuPath.c_str());
        wxString invokeMainMenuMacro = _T("!") + menuPath.Mid(0,1);

        // If this is the Edit top menu, examine the last menu item
        // to determine if it can be opened on a command/menu mismatch
        wxString lastMenuItemLabel = wxEmptyString;
        if ( menuPath.StartsWith(_T("Edit")) )
        {   // expand last edit menu item if menu mismatches
            HMENU hEditMenu = GetMSWndMenu( 0, _T("Edit"));
            int hEditMenuKnt = GetMSWndMenuItemCount(hEditMenu);
            if ( hEditMenuKnt)
                lastMenuItemLabel = GetMSWndMenuLabelByPosn(hEditMenu, hEditMenuKnt-1);
            if ( lastMenuItemLabel.Contains(_T("Component"))
                 || lastMenuItemLabel.Contains(_T("Group"))
                 || lastMenuItemLabel.Contains(_T("Entit")) )
                invokeMainMenuMacro << _T("{UP}{RIGHT}");
                //invokeMainMenuMacro << _T("{UP}");
        }
        if ( (not pMenu) || (not pMnuItem) )
        {
            #if defined(LOGGING)
            LOGIT( _T("SendMenuActionsNow() did not find[%s]"), menuPath.c_str());
            #endif
            //wxMessageBox(msg);
            m_pRubyClassHandler->m_pMacroRunner->SendMacroString( invokeMainMenuMacro, wxTheApp->GetTopWindow() );
            break;
        }
        wxString menuLabel = pMnuItem->GetItemLabelText();
        if ( menuLabel.IsEmpty() )
        {
            #if defined(LOGGING)
            LOGIT( _T("SendMenuActionsNow() did not find menuLabel for[%s]"), menuPath.c_str());
            #endif
            //wxMessageBox(msg);
            m_pRubyClassHandler->m_pMacroRunner->SendMacroString( invokeMainMenuMacro, wxTheApp->GetTopWindow() );
            break;
        }
        if ( menuLabel not_eq menuPath.AfterLast(_T('/')) )
        {
            #if defined(LOGGING)
            LOGIT( _T("SendMenuActionsNow() Label mismatch [%s] [%s]"), menuLabel.c_str(),menuPath.AfterLast(_T('/')).c_str());
            #endif
            //msg = wxString::Format(_T("Cannot Apply %s"), menuPath.c_str());
            //wxMessageBox(msg);
            m_pRubyClassHandler->m_pMacroRunner->SendMacroString( invokeMainMenuMacro, wxTheApp->GetTopWindow() );
            break;
        }

        // fetch MSW menu id mapped to our wxWidgets menu id
        int mswID = 0;
        WxId2MSWIdHash::iterator iter = m_WxId2MSWIdMap.find(menuId);
        if ( iter == m_WxId2MSWIdMap.end() )
        {
            //msg = wxString::Format(_T("SendMenuActionsNow() Cannot Find menuId %d"), menuId);
            //wxMessageBox(msg);
            m_pRubyClassHandler->m_pMacroRunner->SendMacroString( invokeMainMenuMacro, wxTheApp->GetTopWindow() );
            break;
        }
        else
            mswID = iter->second;

        // verify wxMenu == MSW current menu items and item is enabled
        wxString mswLabel = GetMSWndMenuLabelByID( 0, mswID);
        wxMenuItem mswMenuItem(pMenu, 0, mswLabel); //dummy for local use
        if ( (menuLabel not_eq mswMenuItem.GetItemLabelText() )
          || ( not pMnuItem->IsEnabled()) )
        {
            #if defined(LOGGING)
            LOGIT(_T("SendMenuActionsNow() Id/Label mismatch for [%s][%s]"), menuPath.c_str(), mswLabel.c_str());
            #endif
            //wxMessageBox(msg);
            m_pRubyClassHandler->m_pMacroRunner->SendMacroString( invokeMainMenuMacro, wxTheApp->GetTopWindow() );
            break;
        }

        #if defined(LOGGING)
        LOGIT( _T("SendMenuActionsNow[%s]wxID[%d]mswID[%d]"), menuLabel.c_str(), menuId, mswID);
        #endif

        //-return m_SendMenuActionsAry.GetCount(); //debugging

        LRESULT lresult = ::SendMessage( g_hWndSketchUp, WM_COMMAND, mswID, 0);
        if ( lresult ) {;}
    }
    return m_SendMenuActionsAry.GetCount();
}
// ----------------------------------------------------------------------------
wxString HiddenFrame::GetMSWndMenuLabelByID(const HMENU hMenuIn, const unsigned menuId)
// ----------------------------------------------------------------------------
{
    // Get label from MS window menu item (not wxWindow menu)

    wxString menuLabel = wxEmptyString;
    //HWND hWnd = g_hWndSketchUp ;
    HMENU hMenu = hMenuIn;
    if (hMenu is_eq 0)
        hMenu = ::GetMenu(g_hWndSketchUp);
    if (not hMenu) return menuLabel;
    int iMenuItemCount = GetMenuItemCount (hMenu);

    if ( iMenuItemCount <= 0)
        return menuLabel;

        TCHAR* zBuffer = 0;
        bool rc = false;
    do{
        // Get Item Text length
        MENUITEMINFO mii;
        memset(&mii, 0, sizeof(mii));
        mii.cbSize = sizeof (MENUITEMINFO);
        mii.fMask = MIIM_STRING | MIIM_ID;
        mii.dwTypeData = NULL;

        rc = GetMenuItemInfo (hMenu, menuId, false, &mii);
        if ( 0 == mii.cch ) break; //label length is 0
        if ( false == rc) break;

        // allocate buffer from length
        TCHAR* zBuffer = new TCHAR[mii.cch+1];

        // Get menu label itself
        MENUITEMINFO mii2;
        memset(&mii2, 0, sizeof(mii));
        mii2.cbSize = sizeof (MENUITEMINFO);
        mii2.fMask = MIIM_STRING;
        mii2.dwTypeData = zBuffer;
        mii2.cch = mii.cch+1;

        rc = GetMenuItemInfo (hMenu, menuId, false, &mii2);
        if ( false == rc) break;

        menuLabel = zBuffer;
        menuLabel.Replace(_T("&"), _T(""));

    }while(0);

    if (zBuffer) delete [] zBuffer;

    return menuLabel;
}
    //Doc
    //BOOL GetMenuItemInfo( HMENU hMenu, UINT uItem, BOOL fByPosition, LPMENUITEMINFO lpmii);
    //
    //    typedef struct tagMENUITEMINFO {
    //      UINT    cbSize;
    //      UINT    fMask;
    //      UINT    fType;
    //      UINT    fState;
    //      UINT    wID;
    //      HMENU   hSubMenu;
    //      HBITMAP hbmpChecked;
    //      HBITMAP hbmpUnchecked;
    //      ULONG_PTR dwItemData;
    //      LPTSTR  dwTypeData;
    //      UINT    cch;
    //      HBITMAP hbmpItem;
    //    } MENUITEMINFO, *LPMENUITEMINFO;
// ----------------------------------------------------------------------------
wxString HiddenFrame::GetMSWndMenuLabelByPosn(const HMENU hMenuIn, const unsigned posnIn)
// ----------------------------------------------------------------------------
{
    // Get label from MS window menu item (not wxWindow menu)

    wxString menuLabel = wxEmptyString;
    HMENU hMenu = hMenuIn;
    if (hMenu is_eq 0)
        hMenu = ::GetMenu(g_hWndSketchUp) ;
    if (not hMenu) return menuLabel;
    int iMenuItemCount = GetMenuItemCount (hMenu);

    if ( iMenuItemCount <= 0)
        return menuLabel;

        TCHAR* zBuffer = 0;
        bool rc = false;
    do{
        // Get Item Text length
        MENUITEMINFO mii;
        memset(&mii, 0, sizeof(mii));
        mii.cbSize = sizeof (MENUITEMINFO);
        mii.fMask = MIIM_STRING | MIIM_ID;
        mii.dwTypeData = NULL;

        rc = GetMenuItemInfo (hMenu, posnIn, true, &mii);
        if ( 0 == mii.cch ) break; //label length is 0
        if ( false == rc) break;

        // allocate buffer from length
        TCHAR* zBuffer = new TCHAR[mii.cch+1];

        // Get menu label itself
        MENUITEMINFO mii2;
        memset(&mii2, 0, sizeof(mii));
        mii2.cbSize = sizeof (MENUITEMINFO);
        mii2.fMask = MIIM_STRING;
        mii2.dwTypeData = zBuffer;
        mii2.cch = mii.cch+1;

        rc = GetMenuItemInfo (hMenu, posnIn, true, &mii2);
        if ( false == rc) break;

        menuLabel = zBuffer;
        menuLabel.Replace(_T("&"), _T(""));

    }while(0);

    if (zBuffer) delete [] zBuffer;

    return menuLabel;
}
// ----------------------------------------------------------------------------
HMENU HiddenFrame::GetMSWndMenu(const HMENU hMenuIn, const wxString menuLabelIn )
// ----------------------------------------------------------------------------
{
    // Get label from MS window menu item (not wxWindow menu)
    HMENU hMenuFound = 0;
    wxString MSWmenuLabel = wxEmptyString;
    HMENU hMenu = hMenuIn;
    if (hMenu is_eq 0)
        hMenu = ::GetMenu(g_hWndSketchUp) ;
    if (not hMenu) return hMenu;
    int iMenuItemCount = GetMenuItemCount (hMenu);

    if ( iMenuItemCount <= 0)
        return 0;

    const int maxLabelLen = 256;
    TCHAR zBuffer[maxLabelLen+1] = {0};
    bool rc = false;

    for (int mPosn = 0; mPosn<iMenuItemCount; ++mPosn )
    {
        // Get Item Text length
        MENUITEMINFO mii;
        memset(&mii, 0, sizeof(mii));
        mii.cbSize = sizeof (MENUITEMINFO);
        mii.fMask = MIIM_STRING | MIIM_ID ;
        mii.dwTypeData = NULL;

        rc = GetMenuItemInfo (hMenu, mPosn, true, &mii);
        if ( 0 == mii.cch ) continue; //label length is 0
        if ( false == rc) continue;

        // Get menu label itself
        MENUITEMINFO mii2;
        memset(&mii2, 0, sizeof(mii));
        mii2.cbSize = sizeof (MENUITEMINFO);
        mii2.fMask = MIIM_STRING;
        mii2.dwTypeData = zBuffer;
        mii2.cch = mii.cch+1;

        rc = GetMenuItemInfo (hMenu, mPosn, true, &mii2);
        if ( false == rc) continue;

        MSWmenuLabel = zBuffer;
        MSWmenuLabel.Replace(_T("&"), _T(""));
        if ( wxNOT_FOUND not_eq (MSWmenuLabel.Find(menuLabelIn[0])))
        if (menuLabelIn is_eq MSWmenuLabel.Find(menuLabelIn[0]), menuLabelIn.Length())
        {
            hMenuFound = ::GetSubMenu(hMenu, mPosn);
            break;
        }

    }//for

    return hMenuFound;
}
// ----------------------------------------------------------------------------
int HiddenFrame::GetMSWndMenuItemCount(const HMENU hMenuIn)
// ----------------------------------------------------------------------------
{
    if (not hMenuIn) return 0;
    int knt = ::GetMenuItemCount( hMenuIn );
    if (knt is_eq -1) return 0;
    return knt;
}
// ----------------------------------------------------------------------------
wxString HiddenFrame::FindAppPath(const wxString& argv0, const wxString& cwd, const wxString& appVariableName)
// ----------------------------------------------------------------------------
{
    // Find the absolute path from where this application has been run.
    // argv0 is wxTheApp->argv[0]
    // cwd is the current working directory (at startup)
    // appVariableName is the name of a variable containing the directory for this app, e.g.
    // MYAPPDIR. This is checked first.

    wxString str;

    // Try appVariableName
    if (!appVariableName.IsEmpty())
    {
        str = wxGetenv(appVariableName);
        if (!str.IsEmpty())
            return str;
    }

#if defined(__WXMAC__) && !defined(__DARWIN__)
    // On Mac, the current directory is the relevant one when
    // the application starts.
    return cwd;
#endif

    wxString argv0Str = argv0;
    #if defined(__WXMSW__)
        do{
            if (argv0Str.Contains(wxT(".exe")) ) break;
            if (argv0Str.Contains(wxT(".bat")) ) break;
            if (argv0Str.Contains(wxT(".cmd")) ) break;
            argv0Str.Append(wxT(".exe"));
        }while(0);
    #endif

    if (wxIsAbsolutePath(argv0Str))
    {
        #if defined(LOGGING)
        LOGIT( _T("FindAppPath: AbsolutePath[%s]"), wxPathOnly(argv0Str).GetData() );
        #endif
        return wxPathOnly(argv0Str);
    }
    else
    {
        // Is it a relative path?
        wxString currentDir(cwd);
        if (currentDir.Last() != wxFILE_SEP_PATH)
            currentDir += wxFILE_SEP_PATH;

        str = currentDir + argv0Str;
        if (wxFileExists(str))
        {
            #if defined(LOGGING)
            LOGIT( _T("FindAppPath: RelativePath[%s]"), wxPathOnly(str).GetData() );
            #endif
            return wxPathOnly(str);
        }
    }

    // OK, it's neither an absolute path nor a relative path.
    // Search PATH.

    wxPathList pathList;
    pathList.AddEnvList(wxT("PATH"));
    str = pathList.FindAbsoluteValidPath(argv0Str);
    if (!str.IsEmpty())
    {
        #if defined(LOGGING)
        LOGIT( _T("FindAppPath: SearchPath[%s]"), wxPathOnly(str).GetData() );
        #endif
        return wxPathOnly(str);
    }

    // Failed
    #if defined(LOGGING)
     LOGIT(  _T("FindAppPath: Failed, returning cwd") );
    #endif
    return wxEmptyString;
    //return cwd;
}

// ----------------------------------------------------------------------------
int HiddenFrame::KeyBinderConfig()
// ----------------------------------------------------------------------------
{
    // Show dialog with menu tree and accept user defined keyboard commands

    // verify menuBar
    wxMenuBar* pMenuBar = GetMenuBar();
    if (not pMenuBar)
        return 0;

    m_arrOfProfiles.Cleanup();

    // Verify profile array
    if (0 == m_arrOfProfiles.GetCount())
    {
        //-m_pPrimary = new wxKeyProfile(wxT("Primary"), wxT("Primary Keyprofile"));
        //-m_pPrimary->ImportMenuBarCmd(pMenuBar);

        //- put both keyprofiles into our array
        //-m_arrOfProfiles.Add(m_pPrimary);

        //- by now, attach to this window the primary keybinder
        //-m_arrOfProfiles.SetSelProfile(0);
        //-UpdateArr(arr); We don't attach windows for SketchUp

        wxMenuCmd::Register( pMenuBar );

        // Get executable dir, then "plugins" then "EventRelay"
        m_ExecuteFolder = wxStandardPaths::Get().GetExecutablePath();
        m_ExecuteFolder = m_ExecuteFolder.BeforeLast(wxFILE_SEP_PATH);
        m_UsrCmdFolder = m_ExecuteFolder + wxFILE_SEP_PATH + _T("EventRelay") + wxFILE_SEP_PATH;
        m_UsrCmdFilePath = m_UsrCmdFolder + _T("UserKBCommands.rb");

        // Load User UserKBCommands.rb
        int count = LoadConfigFile( m_UsrCmdFilePath);

        if ( count )
        {
            m_arrOfProfiles.SetSelProfile(0);
            m_arrOfProfiles.GetSelProfile()->ImportMenuBarCmd(m_pMenuBar);
            m_arrOfProfiles.GetSelProfile()->Enable(TRUE);
            #if defined(LOGGING)
            LOGIT( _T("Hiddenframe[%p] menuBar[%p]"), this, m_pMenuBar);
            #endif
        }
        else //no user command definitions
        {
            wxKeyProfile* pPrimary;
            pPrimary = new wxKeyProfile(wxT("Primary"), wxT("Primary Keyprofile"));
            pPrimary->ImportMenuBarCmd(m_pMenuBar);
            m_arrOfProfiles.Add(pPrimary);
        }
        #if defined(LOGGING)
        LOGIT( _T("LoadConfigFile found [%d] shortcuts"), count);
        #endif

    }//if

    // ------------------------------------------------------------------------
    //  OnKeyBinderConfig
    // ------------------------------------------------------------------------
	bool btree = true; //Show menus as a tree structure
	bool baddprofile = true;
	bool bprofiles = false;
	bool bprofileedit = true;

	// setup build flags
	int mode = btree ? wxKEYBINDER_USE_TREECTRL : wxKEYBINDER_USE_LISTBOX;
	if (baddprofile) mode |= wxKEYBINDER_SHOW_ADDREMOVE_PROFILE;
	if (bprofileedit) mode |= wxKEYBINDER_ENABLE_PROFILE_EDITING;

	int exitcode = 0; int sel = 0;
	{// Block begin to allocate dialog resources

        //// Assume hwnd of SketchUp for child dialog
        ////WXHWND hwndHiddenFrame = GetHWND(); //save HiddenFrame window handle
        ////::EnableWindow(g_hWndSketchUp, false);
        ////SetHWND((WXHWND)g_hWndSketchUp );   //replace it with SketchUp hwnd

        // disbable SketchUp while dialog is active
        wxWindow win;
        win.SetHWND((WXHWND)g_hWndSketchUp );
        win.Enable(false);

		KeyBinderDialog dlg(m_arrOfProfiles, this, wxT("User Keyboard Commands"), mode | wxKEYBINDER_SHOW_APPLYBUTTON);

		// does the user wants to enable key profiles ?
		dlg.m_p->EnableKeyProfiles(bprofiles);

        // A bug in the Sizer code is allowing empty space at the top of the
        // dlg. A resize gets rid of it, and looks better (to me).
        wxSize dlgSize = dlg.GetSize();
        dlgSize.x -= 100; //dlgSize.y -= 1;
        dlg.SetSize(dlgSize);

		if ((exitcode = dlg.ShowModal()) == wxID_OK)
		{
			// update our array (we gave a copy of it to MyDialog)
			m_arrOfProfiles = dlg.m_p->GetProfiles();
		}

        //Clean up else caller can't use the main window

        //Reset focus back to SketchUp
        win.Enable(true);
        win.SetFocus(); //<-- Because Sketchup loses focus after dialog closes
        win.SetHWND(0);
        ////::EnableWindow(g_hWndSketchUp, true);

	}//Block end to free dialog resources

	if (exitcode == wxID_OK) {

		// select the right keyprofile
		sel = m_arrOfProfiles.GetSelProfileIdx();
		//wxMessageBox(wxString::Format(wxT("Selected the %d-th profile (named '%s')."),
		//	sel+1, m_arrOfProfiles.Item(sel)->GetName().c_str()),
		//	wxT("Profile selected"));

		// --------------------------------------------------------
		// Now save a Ruby script containing the user defined commands
		// --------------------------------------------------------
		// def UserKBCommand_usercmdstring
		//      menuID = GetMenuId("menu/path/matching/user/command");
		//      Sketchup.send_action(menuID);
		// end
		// RegisterUserCommand("usercmdstring", self, ":UserKBCommand_usercmdstring");

        m_arrOfProfiles.Save( m_UsrCmdFilePath );
		// get the cmd count
		int total = 0;
		for (int i=0; i<m_arrOfProfiles.GetCount(); i++)
			total += m_arrOfProfiles.Item(i)->GetCmdCount();

        int cmdShortcutCount = 0;
		for (int i=0; i< m_arrOfProfiles.GetCount(); ++i)                      //wxKeyProfile/wxKeyBinder count
			for (int j=0; j < m_arrOfProfiles.Item(i)->GetCmdCount(); ++j )    //wxCmdArray count
                    cmdShortcutCount += m_arrOfProfiles.Item(i)->GetArray()->Item(j)->GetShortcutCount();

        //-return total;
        return cmdShortcutCount;
	}//if

	return (exitcode == wxID_OK);
}
// ----------------------------------------------------------------------------
int HiddenFrame::LoadConfigFile(wxString fileNameIn)
// ----------------------------------------------------------------------------
{
    // Read the UserKBCommands.rb file and parse the command shortcuts

    int total = 0;
    int cmdShortcutCount = 0;

	if ( m_arrOfProfiles.Load( fileNameIn ))
	{
		// get the cmd count
		for (int i=0; i<m_arrOfProfiles.GetCount(); i++)
			total += m_arrOfProfiles.Item(i)->GetCmdCount();

		if (total == 0)
		{
            #if defined(LOGGING)
			//wxMessageBox(wxString::Format(
			//		wxT("No keyprofiles have been found...\n")
			//		wxT("A default keyprofile will be set.")));
            #endif
			wxKeyProfile *p = new wxKeyProfile(wxT("Default"));
			p->ImportMenuBarCmd(GetMenuBar());
			m_arrOfProfiles.Add(p);
		}
		else //total > 0
		{
		    // wxMessage box appears to be causing SketchUp to loop on exit.
		    // Note that the dlg's parent is HiddenFrame, not SketchUp and
		    // wxMessageBox appears just before KeyBinder dlg which is the parent.
		    // Commenting out the wxMessageBox has solved the problem for now.
		    // 2009/01/9
		    // Disabling the buggy wxWidgets wxEntryCleanup() routine solved this.

            #if defined(LOGGING)
                //wxMessageBox(wxString::Format(
                //        wxT("All the [%d] keyprofiles have been correctly loaded ")
                //        wxT("(%d commands in total).\n")
                //        wxT("The %d-th loaded profile ('%s') will be applied."),
                //        m_arrOfProfiles.GetCount(), total, m_arrOfProfiles.GetSelProfileIdx()+1,
                //        m_arrOfProfiles.GetSelProfile()->GetName().c_str()),
                //            wxT("Success"));
            #endif
		}


		for (int i=0; i< m_arrOfProfiles.GetCount(); ++i)                      //wxKeyProfile/wxKeyBinder count
			for (int j=0; j < m_arrOfProfiles.Item(i)->GetCmdCount(); ++j )    //wxCmdArray count
                cmdShortcutCount += m_arrOfProfiles.Item(i)->GetArray()->Item(j)->GetShortcutCount();
	}
	else
	{
        #if defined(LOGGING)
		//wxMessageBox(wxT("Something wrong while loading !"),
		//	wxT("Error"), wxOK | wxICON_ERROR);
        #endif
	}

	//-return total;
	return cmdShortcutCount;
}
// ----------------------------------------------------------------------------
// keybindings dialog: a super-simple wrapper for wxKeyConfigPanel
// ----------------------------------------------------------------------------
KeyBinderDialog::KeyBinderDialog(wxKeyProfileArray &prof,
				   wxWindow *parent, const wxString &title, int mode)
// ----------------------------------------------------------------------------
	:wxDialog(parent, -1, title, wxDefaultPosition, wxDefaultSize,
		wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxSTAY_ON_TOP)
{
	// we can do our task in two ways:
	// 1) we can use wxKeyConfigPanel::ImportMenuBarCmd which gives
	//    better appearances (for me, at least, :-))
	// 2) we can use wxKeyConfigPanel::ImportKeyBinderCmd

	// STEP #1: create a simple wxKeyConfigPanel
	m_p = new wxKeyConfigPanel(this, wxID_ANY, wxDefaultPosition,
                               wxDefaultSize, wxTAB_TRAVERSAL|mode);

	// STEP #2: add a profile array to the wxKeyConfigPanel
	m_p->AddProfiles(prof);

	// STEP #3: populate the wxTreeCtrl widget of the panel
	// note: here, this *only* populates the tree ctrl, not the wxKeyProfile
	m_p->ImportMenuBarCmd(((wxFrame*)parent)->GetMenuBar());

	// and embed it in a little sizer
	wxBoxSizer *main = new wxBoxSizer(wxVERTICAL);
	main->Add(m_p, 1, wxGROW);
	SetSizer(main);
	main->SetSizeHints(this);

	// this is a little modification to make dlg look nicer
	wxSize sz(GetSizer()->GetMinSize());
	SetSize(-1, -1, (int)(sz.GetWidth()*1.3), (int)(sz.GetHeight()*1.1));
	CenterOnScreen();
}
// ----------------------------------------------------------------------------
KeyBinderDialog::~KeyBinderDialog()
// ----------------------------------------------------------------------------
{
    //dtor
}
// ----------------------------------------------------------------------------
void KeyBinderDialog::OnApply(wxCommandEvent &)
// ----------------------------------------------------------------------------
{
    EndModal(wxID_OK);
}

